package com.tianji.learning.service.impl;

import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constant.LearningConstants;
import com.tianji.learning.constant.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author XinxuanZhuo
 * @since 2024-03-07
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    private final StringRedisTemplate stringRedisTemplate;

    private final UserClient userClient;


    /**
     * 分页查询指定赛季的积分排行榜
     */
    @Override
    public PointsBoardVO queryPointsBoardBySeason(PointsBoardQuery query) {
        // 查询历史排行榜或本赛季排行榜
        Long seasonId = query.getSeason();
        boolean itsCurrentSeason = seasonId == null || seasonId == 0;
        // 拼接key
        LocalDateTime now = LocalDateTime.now();
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX
                + now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        // 获取当前用户的排行榜信息
        PointsBoard myBoard = itsCurrentSeason ?
                // 查询当前榜单（Redis）【当前用户】
                queryMyCurrentBoard(key) :
                // 查询历史榜单（MySQL）【当前用户】
                queryMyHistoryBoard(seasonId);
        // 获取所有排行榜数据
        List<PointsBoard> list = itsCurrentSeason ?
                // 查询当前榜单（Redis）【总榜】
                queryCurrentBoardList(key, query.getPageNo(), query.getPageSize()) :
                // 查询历史榜单（MySQL）【总榜】
                queryHistoryBoardList(query);
        // 封装vo返回
        // 封装VO
        PointsBoardVO vo = new PointsBoardVO();
        // 处理我的榜单信息
        if (myBoard != null) {
            vo.setPoints(myBoard.getPoints());
            vo.setRank(myBoard.getRank());
        }
        // 处理榜单
        // 为空直接返回
        if (CollUtils.isEmpty(list)) {
            return vo;
        }
        // 不为空 完善表单的用户信息
        Set<Long> userIds = list.stream().map(PointsBoard::getUserId).collect(Collectors.toSet());
        Map<Long, String> userInfoMap = queryUserInfoByUserIds(userIds);

        // 转换VO
        List<PointsBoardItemVO> items = new ArrayList<>(list.size());
        for (PointsBoard p : list) {
            PointsBoardItemVO v = new PointsBoardItemVO();
            v.setPoints(p.getPoints());
            v.setRank(p.getRank());
            v.setName(userInfoMap.getOrDefault(p.getUserId(), ""));
            items.add(v);
        }
        vo.setBoardList(items);
        return vo;
    }


    /**
     * 创建上个月的排行榜表
     */
    @Override
    public void createPointsBoardTableBySeason(Integer seasonId) {
        String tableName = LearningConstants.POINTS_BOARD_TABLE_PREFIX + seasonId;
        this.baseMapper.createPointsBoardTableBySeason(tableName);
    }


    /**
     * 查询当前榜单（Redis）【当前用户】
     */
    private PointsBoard queryMyCurrentBoard(String key) {
        // 绑定key
        BoundZSetOperations<String, String> operations = stringRedisTemplate.boundZSetOps(key);
        // 获取当前用户信息
        String userId = UserContext.getUser().toString();
        //查询积分
        Double points = operations.score(userId);
        //查询排名
        Long rank = operations.rank(userId);
        //封装返回
        PointsBoard p = new PointsBoard();
        p.setPoints(points == null ? 0 : points.intValue());
        p.setRank(rank == null ? 0 : rank.intValue() + 1);
        return p;
    }


    /**
     * 查询当前榜单（Redis）【总榜】
     * （zset是一个升序 不重复的集合）
     * 要点：从哪里开始查，查到哪里 索引开始和计数的区别
     */
    @Override
    public List<PointsBoard> queryCurrentBoardList(String key, Integer pageNo, Integer pageSize) {
        // 计算分页
        int from = (pageNo - 1) * pageSize;
        int end = from + pageSize - 1;
        // 查询
        Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet().
                reverseRangeWithScores(key, from, end);
        if (CollUtils.isEmpty(tuples)) {
            return CollUtils.emptyList();
        }
        //封装
        int rank = from + 1;
        List<PointsBoard> list = new ArrayList<>(tuples.size());
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            String userId = tuple.getValue();
            Double points = tuple.getScore();
            if (userId == null || points == null) {
                continue;
            }
            PointsBoard p = new PointsBoard();
            p.setUserId(Long.valueOf(userId));
            p.setPoints(points.intValue());
            p.setRank(rank++);
            list.add(p);
        }
        return list;
    }


    /**
     * 查询历史榜单（MySQL）【当前用户】
     */
    private PointsBoard queryMyHistoryBoard(Long seasonId) {
        // TODO
        return null;
    }

    /**
     * 查询历史榜单（MySQL）【总榜】
     */
    private List<PointsBoard> queryHistoryBoardList(PointsBoardQuery query) {
        // TODO
        return null;
    }

    /**
     * 调用远程用户服务查询用户信息
     */
    private Map<Long, String> queryUserInfoByUserIds(Set<Long> userIds) {
        Map<Long, String> resultMap = new HashMap<>(userIds.size());
        List<UserDTO> userDTOS = userClient.queryUserByIds(userIds);
        if (CollUtils.isNotEmpty(userDTOS)) {
            resultMap = userDTOS.stream()
                    .collect(Collectors.toMap(UserDTO::getId, UserDTO::getName));
        }
        return resultMap;

    }


}
